package com.iqiju.guagua.photo.wx.mini.service;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.iqiju.guagua.photo.wx.mini.common.Results;
import com.iqiju.guagua.photo.wx.mini.mapper.WxMiniUserMapper;
import com.iqiju.guagua.photo.wx.mini.model.Jwt;
import com.iqiju.guagua.photo.wx.mini.model.WxMiniApp;
import com.iqiju.guagua.photo.wx.mini.model.WxMiniUser;
import com.iqiju.guagua.photo.wx.mini.utils.AesUtils;
import com.iqiju.guagua.photo.wx.mini.utils.JwtUtils;
import com.iqiju.guagua.photo.wx.mini.utils.Sha1Utils;
import com.iqiju.guagua.photo.wx.mini.vo.LoginVO;
import com.iqiju.guagua.photo.wx.mini.vo.UserAuthVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate;

import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;

@Slf4j
@Service
public class WxMiniUserService extends ServiceImpl<WxMiniUserMapper, WxMiniUser> {

    public static final String GET_ACCESS_TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET";

    private static final String CODE_SESSION_URL = "https://api.weixin.qq.com/sns/jscode2session?appid=APPID&secret=SECRET&js_code=JSCODE&grant_type=authorization_code";

    @Autowired
    private WxMiniAppService wxMiniAppService;

    @Autowired
    private RestTemplate restTemplate;

    @Autowired
    private IDService idService;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    public Results<LoginVO> login(String wxMiniAppId, String code, String channel, String shareUserId) {
        if (wxMiniAppId==null||wxMiniAppId.trim().length()<=0){
            log.error("参数 wxMiniAppId 为空");
            return Results.error("参数 wxMiniAppId 不能为空");
        }
        log.info("wxMiniAppId=[{}]",wxMiniAppId);
        if (code==null||code.trim().length()<=0){
            log.error("参数 code 为空");
            return Results.error("参数 code 不能为空");
        }
        log.info("code=[{}]",code);
        if (channel==null||channel.trim().length()<=0){
            log.error("参数 channel 为空");
            return Results.error("参数 channel 不能为空");
        }
        log.info("channel=[{}]",channel);
        if (shareUserId==null||shareUserId.trim().length()<=0){
            log.error("参数 shareUserId 为空");
            return Results.error("参数 shareUserId 不能为空");
        }
        log.info("shareUserId=[{}]",shareUserId);

        WxMiniApp wxMiniApp = wxMiniAppService.selectById(Long.parseLong(wxMiniAppId));
        if (wxMiniApp==null){
            log.error("数据库中未配置小程序 wxMiniAppId={}", wxMiniAppId);
            return Results.paramError("请在数据库中配置小程序 wxMiniAppId="+wxMiniAppId);
        }



        /* 获取 openid session_key */
        JSONObject authCode2Session = getAuthCode2Session(wxMiniApp, code);
        if (null == authCode2Session){
            return Results.error("获取openid & session_key失败");
        }

        //用户openid
        String openid = authCode2Session.getString("openid");

        QueryWrapper<WxMiniUser> userQueryWrapper = new QueryWrapper<>();
        userQueryWrapper.eq("wx_mini_app_id",wxMiniAppId);
        userQueryWrapper.eq("openid",openid);
        List<WxMiniUser> userList = list(userQueryWrapper);

        WxMiniUser wxMiniUser = null;
        if (userList.isEmpty()){
            /* 保存到用户表 */
            wxMiniUser = new WxMiniUser();
            wxMiniUser.setId(idService.id());
            wxMiniUser.setWxMiniAppId(Long.valueOf(wxMiniAppId));
            wxMiniUser.setOpenid(openid);
            wxMiniUser.setCreateTime(new Date());
            wxMiniUser.setChannelId(channel);
            wxMiniUser.setShareUserId(Long.valueOf(shareUserId));
            boolean saveResult = false;
            try {
                saveResult = save(wxMiniUser);
            } catch (Exception e) {
                log.error("注册异常！wxMiniAppId={}  code={}  channel={}  shareUserId={}", wxMiniAppId,code,channel,shareUserId);
                log.error(e.getMessage(), e);
                return Results.error("注册 异常");
            }
            log.info("注册到用户表结果=[{}]", saveResult);
            if (!saveResult){
                log.error("注册用户失败！wxMiniAppId={}  code={}  channel={}  shareUserId={}", wxMiniAppId,code,channel,shareUserId);
                return Results.error("注册用户失败！请检查参数并重试");
            }
        }else{
            wxMiniUser = userList.get(0);
        }

        /* 返回本地token */
        String jwt = null;
        try {
            jwt = loginSuccess(wxMiniUser);
            log.info("登录成功！");
            return Results.success(new LoginVO(jwt,String.valueOf(wxMiniUser.getId())));
        } catch (Exception e) {
            log.error("登录获取jwt异常！wxMiniAppId={}  code={}  channel={}  shareUserId={}", wxMiniAppId,code,channel,shareUserId);
            log.error(e.getMessage(), e);
            return Results.error("返回 token 异常");
        }
    }

    /** 初始化access token */
    public boolean setAccessToken(WxMiniApp wxMiniApp) {
        if (wxMiniApp.getAccessToken()!=null&&wxMiniApp.getAccessToken().trim().length()>0) {
            Long expires = wxMiniApp.getExpires();
            if (expires > System.currentTimeMillis()){
                log.info("access_token 还未过期！");
                return true;
            }
            log.warn("access_token 已经过期！");
        }

        //删除缓存
        wxMiniAppService.delWxMiniAppKey(String.valueOf(wxMiniApp.getId()));

        String url = GET_ACCESS_TOKEN_URL.replace("APPID",wxMiniApp.getAppId()).replace("APPSECRET",wxMiniApp.getAppSecret());

        JSONObject accessTokenObject = null;
        try {
            accessTokenObject = restTemplate.getForObject(url, JSONObject.class);
        } catch (RestClientException e) {
            log.error("setAccessToken 获取 accessTokenObject异常！appid=[{}]   appsecret=[{}]",wxMiniApp.getAppId(),wxMiniApp.getAppSecret());
            log.error(e.getMessage(), e);
            return false;
        }

        String accessToken = accessTokenObject.getString("access_token");
        if (accessToken!=null&&accessToken.trim().length()>0) {
            log.info("获取access_token 成功!  msg={}", accessTokenObject.toJSONString());
            long expire = accessTokenObject.getLong("expires_in");
            wxMiniApp.setAccessToken(accessToken);
            wxMiniApp.setExpires(expire*1000+System.currentTimeMillis());
            return wxMiniAppService.updateById(wxMiniApp);
        }else{
            String errCode = accessTokenObject.getString("errcode");
            String errMsg = accessTokenObject.getString("errmsg");
            log.error("获取access_token 失败! err_code={}     err_msg={}",errCode,errMsg);
            return false;
        }
    }

    /** 获取 openid session_key */
    private JSONObject getAuthCode2Session(WxMiniApp wxMiniApp, String code) {
        String url = CODE_SESSION_URL.replace("APPID",wxMiniApp.getAppId()).replace("SECRET",wxMiniApp.getAppSecret()).replace("JSCODE",code);
        JSONObject codeObject = null;
        try {
            codeObject = restTemplate.getForObject(url, JSONObject.class);
        } catch (RestClientException e) {
            log.error("getAuthCode2Session 获取 codeObject异常！appid=[{}]   appsecret=[{}] code=[{}]",wxMiniApp.getAppId(),wxMiniApp.getAppSecret(), code);
            log.error(e.getMessage(), e);
            return null;
        }
        String openid = codeObject.getString("openid");
        if (openid!=null&&openid.length()>0){
            log.info("获取成功！ codeObject=[{}]",codeObject.toJSONString());
            return codeObject;
        }else{
            Integer errcode = codeObject.getInteger("errcode");
            String errmsg = codeObject.getString("errmsg");
            log.error("获取openid 失败  errcode=[{}]  errmsg=[{}]",errcode,errmsg);
            return null;
        }
    }

    /** 生成jwt */
    public String loginSuccess(WxMiniUser user) throws Exception {
        // 获取登录成功信息
        user.setPassword(null);
        long now = System.currentTimeMillis();
        JSONObject payload = new JSONObject();
        payload.put("iss","GUAGUA_PHOTO"); //签发人
        payload.put("aud",user.getId()); //受众
        long expireTime = now + JwtUtils.EXPIRE_TIME;
        payload.put("exp",expireTime); //过期时间
        payload.put("nbf",now); //生效时间
        payload.put("iat",now); //签发时间
        payload.put("jti", user.getId()); //编号
        payload.put("sub","GUAGUA_PHOTO_LOGIN"); //主题
        payload.put("user",user); //用户对象
        log.info("登录成功！  用户id=[{}]   注册时间 reg_time=[{}]=[{}]   过期时间 expire_time=[{}]=[{}]", user.getId(), now, fromLongToLocalDateTime(now).toString(), expireTime, fromLongToLocalDateTime(expireTime).toString());
        return new Jwt(payload.toJSONString()).toString();
    }



    private static LocalDateTime fromLongToLocalDateTime(long timestamp) {
        Instant instant = Instant.ofEpochMilli(timestamp);
        ZoneId zone = ZoneId.systemDefault();
        return LocalDateTime.ofInstant(instant, zone);
    }














    public Results auth(UserAuthVO userAuthVO) {
        if (userAuthVO==null){
            log.error("参数 userAuthVO 为空");
            return Results.error("参数不能为空");
        }
        String userId = userAuthVO.getUserId();
        if (userId==null || userId.trim().length()<=0) {
            log.error("登录接口 参数 userId 为空！");
            return Results.paramError("登录接口 参数 userId 不能为空！");
        }
        String wxMiniAppId = userAuthVO.getWxMiniAppId();
        if (wxMiniAppId==null || wxMiniAppId.trim().length()<=0) {
            log.error("登录接口 参数 wxMiniAppId 为空！");
            return Results.paramError("登录接口 参数 wxMiniAppId 不能为空！");
        }
        String code = userAuthVO.getCode();
        if (code==null || code.trim().length()<=0) {
            log.error("登录接口 参数 code 为空！");
            return Results.paramError("登录接口 参数 code 不能为空！");
        }
        String rawData = userAuthVO.getRawData();
        if (rawData==null || rawData.trim().length()<=0) {
            log.error("登录接口 参数 rawData 为空！");
            return Results.paramError("登录接口 参数 rawData 不能为空！");
        }
        String signature = userAuthVO.getSignature();
        if (signature==null || signature.trim().length()<=0) {
            log.error("登录接口 参数 signature 为空！");
            return Results.paramError("登录接口 参数 signature 不能为空！");
        }
        String encryptedData = userAuthVO.getEncryptedData();
        if (encryptedData==null || encryptedData.trim().length()<=0) {
            log.error("登录接口 参数 encryptedData 为空！");
            return Results.paramError("登录接口 参数 encryptedData 不能为空！");
        }
        String iv = userAuthVO.getIv();
        if (iv==null || iv.trim().length()<=0) {
            log.error("登录接口 参数 iv 为空！");
            return Results.paramError("登录接口 参数 iv 不能为空！");
        }
        WxMiniUser wxMiniUser = selectById(Long.parseLong(userId));
        if (wxMiniUser==null){
            log.error("登录接口 参数 userId 错误！");
            return Results.paramError("登录接口 参数 userId 错误！");
        }


        WxMiniApp wxMiniApp = wxMiniAppService.selectById(Long.parseLong(wxMiniAppId));
        if (wxMiniApp==null){
            log.error("数据库中未配置小程序 wxMiniAppId={}", wxMiniAppId);
            return Results.paramError("请在数据库中配置小程序 wxMiniAppId="+wxMiniAppId);
        }

        /* 获取 openid session_key */
        JSONObject authCode2Session = getAuthCode2Session(wxMiniApp, code);
        if (null == authCode2Session){
            return Results.error("获取openid & session_key失败");
        }

        String sessionKey = authCode2Session.getString("session_key");

        /* 验证签名 */
        String localSignature = Sha1Utils.sha1(rawData + sessionKey);
        if (localSignature.equals(signature)){
            log.info("签名验证通过！");
        }else{
            log.error("签名验证失败！");
            log.error("signature={}", signature);
            log.error("localSignature={}", localSignature);
            return Results.error("用户签名验证失败");
        }

        /* 解密数据 */
        log.info("开始解密  encryptedData={}", encryptedData);
        log.info("开始解密  sessionKey={}", sessionKey);
        log.info("开始解密  iv={}", iv);
        String userData = AesUtils.aesCbcDecode(encryptedData, sessionKey, iv);
        if (userData==null||userData.trim().length()<=0){
            return Results.error("数据解密失败");
        }
        log.info("解密后数据 = {}", userData);
        JSONObject userJson = JSON.parseObject(userData);

        JSONObject watermark = userJson.getJSONObject("watermark");
        String watermarkAppid = watermark.getString("appid");
        Long watermarkTimestamp = watermark.getLong("timestamp") * 1000;
        long now = System.currentTimeMillis();
        long abs = Math.abs(now - watermarkTimestamp);
        log.info("time=[{}]",watermarkTimestamp);
        log.info("now=[{}]",now);
        if (!wxMiniApp.getAppId().equals(watermarkAppid) || abs>(1000*60)){
            return Results.error("数据水印无效");
        }

        //删除用户缓存
        delWxUserKey(userId);


        String userUnionId = userJson.getString("unionId");

        wxMiniUser.setUnionid(userUnionId);
        wxMiniUser.setNickName(userJson.getString("nickName"));
        wxMiniUser.setAvatarUrl(userJson.getString("avatarUrl"));
        wxMiniUser.setGender(userJson.getString("gender"));
        wxMiniUser.setCountry(userJson.getString("country"));
        wxMiniUser.setProvince(userJson.getString("province"));
        wxMiniUser.setCity(userJson.getString("city"));
        wxMiniUser.setLanguage(userJson.getString("language"));
        updateById(wxMiniUser);

        String key = getHaveAuthTodayKey(userId);
        stringRedisTemplate.opsForValue().set(key,"1",24, TimeUnit.HOURS);

        return Results.success();
    }









    public Results haveAuthToday(String userId) {
        if (userId==null||userId.trim().length()<=0){
            return Results.paramError("参数 userId 不能为空");
        }
        String key = getHaveAuthTodayKey(userId);
        String haveAuthValue = stringRedisTemplate.opsForValue().get(key);
        if (haveAuthValue==null||haveAuthValue.trim().length()<=0){
            return Results.success("0");
        }else{
            return Results.success("1");
        }
    }


    private String getHaveAuthTodayKey(String userId){
        DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd");
        LocalDate now = LocalDate.now();
        String formatNow = dtf.format(now);
        return "IQIJU_MINI_APP_AUTH_"+formatNow+"_"+userId;
    }




    public WxMiniUser selectById(long id){
        String key = getWxUserKey(String.valueOf(id));
        String userValue = stringRedisTemplate.opsForValue().get(key);
        WxMiniUser wxMiniUser = null;
        if (userValue!=null&&userValue.trim().length()>0){
            wxMiniUser = JSON.parseObject(userValue, WxMiniUser.class);
            return wxMiniUser;
        }
        wxMiniUser = getById(id);
        stringRedisTemplate.opsForValue().set(key,JSON.toJSONString(wxMiniUser),30,TimeUnit.DAYS);
        return wxMiniUser;
    }


    private String getWxUserKey(String id){
        return "IQIJU_MINI_APP_USER_"+id;
    }

    private void delWxUserKey(String id){
        stringRedisTemplate.delete(getWxUserKey(id));
    }






















    public Results<LoginVO> login2(String wxMiniAppId) {
        if (wxMiniAppId==null||wxMiniAppId.trim().length()<=0){
            log.error("参数 wxMiniAppId 为空");
            return Results.error("参数 wxMiniAppId 不能为空");
        }
        log.info("wxMiniAppId=[{}]",wxMiniAppId);

        WxMiniApp wxMiniApp = wxMiniAppService.selectById(Long.parseLong(wxMiniAppId));
        if (wxMiniApp==null){
            log.error("数据库中未配置小程序 wxMiniAppId={}", wxMiniAppId);
            return Results.paramError("请在数据库中配置小程序 wxMiniAppId="+wxMiniAppId);
        }




        WxMiniUser wxMiniUser = new WxMiniUser();
        wxMiniUser.setId(idService.id());
        wxMiniUser.setWxMiniAppId(Long.valueOf(wxMiniAppId));
        wxMiniUser.setCreateTime(new Date());
        boolean saveResult = false;
        try {
            saveResult = save(wxMiniUser);
        } catch (Exception e) {
            log.error("注册异常！wxMiniAppId={} ", wxMiniAppId);
            log.error(e.getMessage(), e);
            return Results.error("注册 异常");
        }
        log.info("注册到用户表结果=[{}]", saveResult);
        if (!saveResult){
            log.error("注册用户失败！wxMiniAppId={}  ", wxMiniAppId);
            return Results.error("注册用户失败！请检查参数并重试");
        }

        /* 返回本地token */
        String jwt = null;
        try {
            jwt = loginSuccess(wxMiniUser);
            log.info("登录成功！");
            return Results.success(new LoginVO(jwt,String.valueOf(wxMiniUser.getId())));
        } catch (Exception e) {
            log.error("登录获取jwt异常！wxMiniAppId={}", wxMiniAppId);
            log.error(e.getMessage(), e);
            return Results.error("返回 token 异常");
        }
    }
}
